package com.youlu.campus.service.qq.impl;

import com.alibaba.fastjson.JSON;
import com.mongodb.client.result.UpdateResult;
import com.youlu.campus.base.exception.AccountException;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.base.user.enums.UserStatusEnum;
import com.youlu.campus.common.utils.WebAuthUtils;
import com.youlu.campus.entity.QQConfigInfo;
import com.youlu.campus.entity.QQMiniUserPO;
import com.youlu.campus.entity.UserPO;
import com.youlu.campus.entity.VO.res.UserResponse;
import com.youlu.campus.entity.dto.qq.QQMiniAccessTokenResult;
import com.youlu.campus.entity.dto.qq.QQMiniLoginResult;
import com.youlu.campus.service.qq.QQConfigInfoService;
import com.youlu.campus.service.qq.QQMiniUserService;
import com.youlu.campus.service.utils.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class QQMiniUserServiceImpl implements QQMiniUserService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private QQConfigInfoService qQConfigInfoService;
    // QQ  小程序登陆地址
    private String QQ_MINI_LOGIN_URL = "https://api.q.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
    // QQ 小程序获取 AccessToken
    private String QQ_GET_ACCESS_TOKNE = "https://api.q.qq.com/api/getToken?grant_type=client_credential&appid=%s&secret=%s";
    public static final String SESSION_KEY = "com.youlu.qq.mini.token-key:appId:%s:openId:%s";
    public static final String ACCESS_TOKEN_PRE_FIX = "com.luyou.access-token.appId:%s:openId:%s";
    public static final String QQ_ACCESS_TOKEN_PRE_FIX = "com.luyou.qq.access-token.platform:%s.appId:%s";

    @Override
    public UserResponse qqMiniLogin(String code, String appId) {
        QQConfigInfo configInfo = qQConfigInfoService.findByAppId(appId);
        String url = String.format(QQ_MINI_LOGIN_URL, appId, configInfo.getAppSecret(), code);
        String str = HttpClientUtils.getRestTemplate().getForObject(url, String.class);
        log.info("code {} QQ 解析token返回 {}", code, str);
        QQMiniLoginResult dto = JSON.parseObject(str, QQMiniLoginResult.class);
        log.info("QQ小程序平台登录返回 {}", JSON.toJSON(dto));
        if (!"0".equals(dto.errcode)) {
            log.warn("登录码失效。 {}", code);
            throw new AccountException("登录状态已失效，请重新打开小程序登录。");
        }
        if (StringUtils.isEmpty(dto.openid)) {
            log.warn("登录返回失败 code {} ret {} {}", code, dto.errcode, dto.errmsg);
            throw new AccountException("登录出错，请重新打开小程序登录。");
        }
        dto.setAppId(appId);
        UserPO user = checkUser(dto);
        log.info(":>>> :::qq登陆用户账户信息:{}", JSON.toJSONString(user));
        //
        this.setSessionKey(appId, dto.getOpenid(), dto.session_key);
        UserResponse response = new UserResponse();
        BeanUtils.copyProperties(user, response);
        response.setToken(WebAuthUtils.signToken(dto.getOpenid()));
        response.setOpenId(dto.getOpenid());
        response.setBindMobile(user.getCheckedMobile());
        response.setBinded(user.getBinded());
        redisTemplate.opsForValue().set(String.format(ACCESS_TOKEN_PRE_FIX, appId, dto.openid),
                WebAuthUtils.signToken(dto.openid), 24, TimeUnit.HOURS);
        log.info(":>>> 返回的登陆数据:{}", JSON.toJSONString(response));
        return response;
    }

    @Override
    public QQMiniUserPO queryUserByOpenId(String appId, String openId) {
        log.info(":>>> 开始查找QQ小程序用户:{},{}", appId, openId);
        if (StringUtils.isBlank(appId) || StringUtils.isBlank(openId)) {
            log.error(":>>> 参数错误");
            throw new BusinessException("参数错误");
        }
        Query query = new Query().addCriteria(Criteria.where("openId").is(openId).and("appId").is(appId));
        return mongoTemplate.findOne(query, QQMiniUserPO.class);
    }

    @Override
    public void setSessionKey(@NotNull String appId, @NotNull String openId, String sessionKey) {
        log.info(":>>> 设置QQ小程序SessionKey:appId:{},openId:{}", appId, openId);
        String key = String.format(SESSION_KEY, appId, openId);
        redisTemplate.opsForValue().set(key, sessionKey, 2, TimeUnit.HOURS);
    }

    @Override
    public String getSessionKey(@NotNull String appId, @NotNull String openId) {
        String key = String.format(SESSION_KEY, appId, openId);
        Object o = redisTemplate.opsForValue().get(key);
        if (Objects.isNull(o)) {
            log.info("session key not found , {}", openId);
            throw new BusinessException("获取sessionKey失败，请重新登录");
        }
        return (String) o;
    }

    @Override
    public void updateUserInfoByAccountId(QQMiniUserPO req) {
        log.info(":>>> 开始更新用户信息:{}", JSON.toJSONString(req));
        if (Objects.isNull(req) || StringUtils.isBlank(req.getAccountInfoId())) {
            log.error(":>>> 修改QQ小程序用户参数错误");
            return;
        }
        Query query = new Query().addCriteria(Criteria.where("accountInfoId").is(req.getAccountInfoId()));
        Update update = new Update();
        boolean toUpdate = false;
        if (StringUtils.isNotBlank(req.getNickName())) {
            update.set("nickName", req.getNickName());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getAvatarUrl())) {
            update.set("avatarUrl", req.getAvatarUrl());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getProvince())) {
            update.set("province", req.getProvince());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getCity())) {
            update.set("city", req.getCity());
            toUpdate = true;
        }
        if (Objects.nonNull(req.getBinded())) {
            update.set("binded", req.getBinded());
        }
        if (toUpdate) {
            mongoTemplate.updateMulti(query, update, QQMiniUserPO.class);
        }

    }

    @Override
    public boolean updateQQMiniUserPO(QQMiniUserPO req) {
        log.info(":>>> 开始修改用户信息:{}", JSON.toJSONString(req));
        if (Objects.isNull(req) || (StringUtils.isBlank(req.getId()) & StringUtils.isBlank(req.getAccountInfoId()))) {
            log.error(":>>> 参数错误");
            return false;
        }
        Query query = new Query();
        if (StringUtils.isNotBlank(req.getId())) {
            query.addCriteria(Criteria.where("id").is(req.getId()));
        }
        if (StringUtils.isNotBlank(req.getAccountInfoId())) {
            query.addCriteria(Criteria.where("accountInfoId").is(req.getAccountInfoId()));
        }
        Update update = new Update();
        boolean toUpdate = false;
        if (Objects.nonNull(req.getBinded())) {
            update.set("binded", req.getBinded());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getNickName())) {
            update.set("nickName", req.getNickName());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getAvatarUrl())) {
            update.set("avatarUrl", req.getAvatarUrl());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(req.getMobile())) {
            update.set("mobile", req.getMobile());
            toUpdate = true;
        }
        if (Objects.nonNull(req.getCheckedMobile())) {
            update.set("checkedMobile", req.getCheckedMobile());
            toUpdate = true;
        }
        if (Objects.nonNull(req.getCheckedMobileTime())) {
            update.set("checkedMobileTime", req.getCheckedMobileTime());
            toUpdate = true;
        }
        if (toUpdate) {
            update.set("updatedTime", new Date());
            UpdateResult updateResult = mongoTemplate.updateMulti(query, update, QQMiniUserPO.class);
            return updateResult.getModifiedCount() > 0 ? true : false;
        }
        return false;
    }

    @Override
    public String getAccessToken(String appId) {
        log.info(":>>> 开始获取accessToken:{}", appId);

        String key = String.format(QQ_ACCESS_TOKEN_PRE_FIX, "qq", appId);
        Object v = redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(v)) {
            return (String) v;
        }
        QQConfigInfo qqConfigInfo = qQConfigInfoService.findByAppId(appId);
        log.info(":>>> qq小程序:{} accessToken 缓存中不存在", appId);
        String url = String.format(QQ_GET_ACCESS_TOKNE, appId, qqConfigInfo.getAppSecret());
        String str = HttpClientUtils.getRestTemplate().getForObject(url, String.class);
        log.info("解析QQ小程序获取AccessToken {}", str);
        QQMiniAccessTokenResult dto = JSON.parseObject(str, QQMiniAccessTokenResult.class);
        if (Objects.isNull(dto) || StringUtils.isBlank(dto.getAccessToken()) || Objects.isNull(dto.getExpiresIn())) {
            log.error(":>>> 获取accessToken失败");
            throw new BusinessException("获取accessToken失败");
        }
        redisTemplate.opsForValue().set(key, dto.getAccessToken(), dto.getExpiresIn() - 150, TimeUnit.SECONDS);
        return dto.getAccessToken();
    }

    @Override
    public QQMiniUserPO queryUserByAccountId(String appId, String accountInfoId) {
        Query query = new Query().addCriteria(Criteria.where("accountInfoId").is(accountInfoId).and("appId").is(appId));
        return mongoTemplate.findOne(query, QQMiniUserPO.class);
    }

    /**
     * 通过openid检查用户是否存在
     *
     * @param dto
     * @return
     */
    private UserPO checkUser(QQMiniLoginResult dto) {
        String openid = dto.openid;
        QQMiniUserPO db = this.queryUserByOpenId(dto.getAppId(), openid);
        if (Objects.isNull(db)) {
            log.info(":>>> 应用:{},openId:{} 用户不存在", dto.getAppId(), openid);
            Query query = new Query().addCriteria(Criteria.where("openId").is(openid).and("appId").is(dto.getAppId()));
            UserPO userPO = mongoTemplate.findOne(query, UserPO.class);
            if (Objects.isNull(userPO)) {
                userPO = new UserPO();
                userPO.setOpenId(openid);
                userPO.setUnionId(dto.unionid);
                userPO.setCreatedTime(new Date());
                userPO.setAppId(dto.getAppId());
                userPO.setCheckedMobile(false);
                userPO.setBinded(false);
                userPO = mongoTemplate.insert(userPO);
            }

            db = new QQMiniUserPO();
            db.setOpenId(openid);
            db.setUnionId(dto.unionid);
            db.setCreatedTime(new Date());
            db.setAppId(dto.getAppId());
            db.setAccountInfoId(userPO.getId());
            db.setCheckedMobile(false);
            db.setBinded(false);
            db = mongoTemplate.insert(db);
            log.debug(":>>> 添加QQ小程序:{} 新用户：{}", dto.getAppId(), openid);
            return userPO;
        } else {
            log.info(":>>> 应用:{},openId:{} 用户已经存在", dto.getAppId(), openid);
            //记录最后登录时间
            db.checkNickNameAndAvatarUrl();
            db.setLastLoginTime(new Date());
            mongoTemplate.save(db);
            if (!UserStatusEnum.NORMAL.getValue().equals(db.getStatus())) {
                throw new BusinessException("该账号不可用！");
            }
            log.debug("已有用户 {}", openid);
            Query query1 = new Query().addCriteria(Criteria.where("id").is(db.getAccountInfoId()));
            UserPO userPO = mongoTemplate.findOne(query1, UserPO.class);
            if (Objects.nonNull(userPO)) {
                userPO.setCheckedMobile(db.getCheckedMobile());
                userPO.setBinded(db.getBinded());
                userPO.setBinded(db.getBinded());
            } else {
                userPO = new UserPO();
                userPO.setOpenId(openid);
                userPO.setUnionId(dto.unionid);
                userPO.setCreatedTime(new Date());
                userPO.setAppId(dto.getAppId());
                userPO.setCheckedMobile(false);
                userPO.setBinded(false);
                userPO = mongoTemplate.insert(userPO);

            }
            log.info(":>>> 账户:{} 信息:{}", db.getAccountInfoId(), JSON.toJSON(userPO));
            return userPO;
        }
    }
}
